//===============================================================================
// Microsoft patterns & practices Enterprise Library Contribution
// Logging Application Block
//===============================================================================
// License: http://www.codeplex.com/entlibcontrib/Project/License.aspx
// Author: Randy
//===============================================================================

using System;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Security.Permissions;

namespace EntLibContrib.Logging.TraceListeners
{
	/// <summary>
	/// Alternative to <see cref="System.Diagnostics.TextWriterTraceListener"/> with No GUID in the file name.
	/// </summary>
	/// <remarks>
	/// Does not need to inherit from <see cref="System.Diagnostics.TextWriterTraceListener"/>,
	/// which would involve defining another private string fileName member.
	/// </remarks>
	[HostProtection(SecurityAction.LinkDemand, Synchronization = true)]
	public class NoGuidTextWriterTraceListener : TraceListener
	{
		#region Fields
		private string fileName;
		private TextWriter writer;
		private byte maxInstances = 1;
		private bool throwMax;
		#endregion

		#region Properties
		/// <summary>
		/// Max Instances in for loop appending count to file name, where 0 is no max
		/// </summary>
		/// <value>The max instances.</value>
		protected byte MaxInstances
		{
			get { return maxInstances; }
			set { maxInstances = value; }
		}

		/// <summary>
		/// Throw Exception if Max Instances reached
		/// </summary>
		/// <value><c>true</c> if [throw max]; otherwise, <c>false</c>.</value>
		protected bool ThrowMax
		{
			get { return throwMax; }
			set { throwMax = value; }
		}

		/// <summary>
		/// Gets or sets the text writer that receives the tracing or debugging output.
		/// </summary>
		/// <value>The writer.</value>
		public TextWriter Writer
		{
			get
			{
				this.EnsureWriter();
				return this.writer;
			}
			set { this.writer = value; }
		}
		#endregion

		#region Construction
		/// <summary>
		///     Initializes a new instance.
		///     with the default TextWriter as the output recipient.
		/// </summary>
		public NoGuidTextWriterTraceListener()
		{
		}
		/// <summary>
		/// Initializes a new instance with a <see cref="Stream"/>.
		/// </summary>
		/// <param name="stream">The stream to write to.</param>
		public NoGuidTextWriterTraceListener(Stream stream)
			: this(stream, string.Empty)
		{
		}
		/// <summary>
		/// Initializes a new instance with a <see cref="TextWriter"/>.
		/// </summary>
		/// <param name="writer">The writer to write to.</param>
		public NoGuidTextWriterTraceListener(TextWriter writer)
			: this(writer, string.Empty)
		{
		}
		/// <summary>
		/// Initializes a new named instance using the file name as the recipient.
		/// </summary>
		/// <param name="fileName">The file name to write to.</param>
		public NoGuidTextWriterTraceListener(string fileName)
		{
			this.fileName = fileName;
		}
		/// <summary>
		/// Initializes a new named instance with a <see cref="Stream"/>.
		/// </summary>
		/// <param name="stream">The stream to write to.</param>
		/// <param name="name">The name.</param>
		public NoGuidTextWriterTraceListener(Stream stream, string name)
			: base(name)
		{
			if (stream == null)
			{
				throw new ArgumentNullException("stream");
			}
			this.writer = new StreamWriter(stream);
		}
		/// <summary>
		/// Initializes a new named instance with a <see cref="TextWriter"/>.
		/// </summary>
		/// <param name="writer">The writer to write to.</param>
		/// <param name="name">The name.</param>
		public NoGuidTextWriterTraceListener(TextWriter writer, string name)
			: base(name)
		{
			if (writer == null)
			{
				throw new ArgumentNullException("writer");
			}
			this.writer = writer;
		}
		/// <summary>
		/// Initializes a new named instance with a file name.
		/// </summary>
		/// <param name="fileName">The file name to write to.</param>
		/// <param name="name">The name.</param>
		public NoGuidTextWriterTraceListener(string fileName, string name)
			: base(name)
		{
			this.fileName = fileName;
		}
		#endregion

		#region Public Methods
		/// <summary>
		/// Closes the <see cref="TextWriter"/> so that it no longer receives tracing or debugging output.
		/// </summary>
		public override void Close()
		{
			if (this.writer != null)
				this.writer.Close();

			this.writer = null;
			base.Close();
		}

		/// <summary>
		/// Flushes the output buffer for the <see cref="TextWriter"/>.
		/// </summary>
		public override void Flush()
		{
			if (this.EnsureWriter())
				this.writer.Flush();
		}

		/// <summary>
		/// Writes a message to this instance's <see cref="TextWriter"/>.
		/// </summary>
		/// <param name="message">A text message to write.</param>
		public override void Write(string message)
		{
			if (this.EnsureWriter())
			{
				if (base.NeedIndent)
					this.WriteIndent();

				this.writer.Write(message);
			}
		}

		/// <summary>
		/// Writes a message to this instance's <see cref="TextWriter"/>
		/// followed by a line terminator. The default line terminator is a carriage
		/// return followed by a line feed (\r\n).
		/// </summary>
		/// <param name="message">A message to write.</param>
		public override void WriteLine(string message)
		{
			if (this.EnsureWriter())
			{
				if (base.NeedIndent)
					this.WriteIndent();

				this.writer.WriteLine(message);
				base.NeedIndent = true;
			}
		}
		#endregion

		#region Protected Methods
		/// <summary>
		/// Disposes this object.
		/// </summary>
		/// <param name="disposing">true to release managed resources</param>
		protected override void Dispose(bool disposing)
		{
			try
			{
				if (disposing)
				{
					if (this.writer != null)
						this.writer.Dispose();

					this.writer = null;
				}
			}
			finally
			{
				base.Dispose(disposing);
			}
		}
		#endregion

		#region Private Methods
		private static Encoding GetEncodingWithFallback(Encoding encoding)
		{
			Encoding encoding2 = (Encoding)encoding.Clone();
			encoding2.EncoderFallback = EncoderFallback.ReplacementFallback;
			encoding2.DecoderFallback = DecoderFallback.ReplacementFallback;
			return encoding2;
		}

		// Suppress FxCop warning message for try-catch block of all errors
		// because an unusualy error in logging should not stop the application from running
		/// <summary>
		/// Ensures the writer.
		/// </summary>
		/// <returns></returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private bool EnsureWriter()
		{
			bool flag = true;
			if (writer == null)
			{
				// Create this Writer using specified file name similar to TextWriterTraceListener
				flag = false;
				if (this.fileName == null)
				{
					return flag;
				}
				Encoding encodingWithFallback = GetEncodingWithFallback(new UTF8Encoding(false));
				const int bufferSize = 0x1000;
				NewName oNewName = null;
				for (int i = 0; i < this.maxInstances || this.maxInstances == 0; i++)
				{
					try
					{
						if (i > 0)
						{
							if (oNewName == null)
								oNewName = new NewName(this.fileName);

							writer = new StreamWriter(oNewName.GetPath(i), true, encodingWithFallback, bufferSize);
						}
						else
							writer = new StreamWriter(this.fileName, true, encodingWithFallback, bufferSize);

						flag = true;
						break;
					}
					catch (IOException)
					{
						// Occurs when file is open in another process
						// Ignore error and try next file name in the for loop
					}
					catch (UnauthorizedAccessException)
					{
						break;
					}
					catch (Exception)
					{
						break;
					}
				}

				if (!flag)
				{
					string fileNameForException = this.fileName;
					this.fileName = null;
					if (this.throwMax)
						throw new LoggingMaxInstancesException("Reached maximum number of instances for log file named " + fileNameForException);
				}
			}
			return flag;
		}
		#endregion

		#region Nested Classes
		private class NewName
		{
			private string directory;
			private string fileNameWithoutExtension;
			private string extension;

			public NewName(string fileName)
			{
				directory = Path.GetDirectoryName(fileName);
				fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
				extension = Path.GetExtension(fileName);
			}

			public string GetPath(int i)
			{
				return Path.Combine(directory, fileNameWithoutExtension + "_" + i + extension);
			}
		}
		#endregion
	}
}
